#pragma once
#include "Common.hpp"
template <class T> // 每次获得一个T对象，T的大小是固定的
class ObjectPool
{
public:
    T *New()
    {
        T *obj = nullptr;
        // 3.还回来的内存也是可以重复使用的,所以_freeList不为空是不用在进行new的
        if (_freeList)
        {
            // 优先把还回来的块对象重复利用
            // 头删
            void *next = *((void **)_freeList); // 第一块内存的头四个字节，存放的是它下一个块的地址
            obj = (T *)_freeList;               // 第一块还回来的内存
            _freeList = next;
            return obj;
        }
        else
        {
            // 1.第一次进来，memory空，要给memory一大块内存
            // 2.空间用完了怎么办
            // 所以我们还是用一个变量记录剩余空间比较好
            // 小于一个对象的时候我们就要进行内存申请
            if (_remainBytes < sizeof(T))
            {
                _remainBytes = 128 * 1024;
                _memory = (char *)malloc(_remainBytes); // 128k
                if (_memory == nullptr)
                {
                    throw std::bad_alloc(); // 失败
                }
            }
            // 给出去一块
            obj = (T *)_memory;
            size_t objSize = sizeof(T) < sizeof(void *) ? sizeof(void *) : sizeof(T);
            _memory += objSize;
            _remainBytes -= objSize;
            // 定位new，显示调用T的构造函数初始化
            // 空间已经有了，不需要定位new像常规new一样去给申请空间，
            // 只需要定位new在已有的空间上调用构造函数构造对象而已。
            new (obj) T;
            return obj; // 将这块内存给该对象用，该对象只是知道了这块地址
        }
    }
    // 还回来一个对象要怎么处理
    void Delete(T *obj)
    {
        obj->~T(); // 显示调用析构函数，清理对象
        // //1.第一次_freelist就是NULL，freelist指向第一个内存块
        // //第一个内存块的头四个字节指向空，表示结束
        // //2.第二次呢？直接头插，不需要去找尾
        // //由于头插也可以使用第一种情况，所以就直接都用头插就好了
        // if(_freeList ==nullptr)
        // {
        //     _freeList =obj;
        //     //如何表示头四个字节
        //     //*(int*) obj = nullptr;//*(int*) 强转成一个int* 解引用那就是看后面四个字节了
        //     //然后让这四个字节存下一个内存块的地址/nullptr，当然这是(32bit)上可以的
        //     //（64bit）上就不行了，需要8字节
        //     *(void**) obj=nullptr//这样使用2级指针就行了就行了，obj就是一个void**类型了
        //     //void** 解引用看void*有多大，指针大小在（32bit）为4字节（64bit）为8字节，这样就解决了
        // }
        // else
        // {
        //     *(void**) obj = _freeList;
        //     _freeList = obj;
        // }
        // 都使用头插
        *(void **)obj = _freeList;
        _freeList = obj;
    }

private:
    char *_memory = nullptr; // 一大块的内存，用的时候就切给申请的对象
    // char是一个字节，切的时候比较进准
    size_t _remainBytes = 0;   // 表示剩余的
    void *_freeList = nullptr; // 管理还回来的内存，该指针指向第一个内存块
    // 剩下的，内存块的头四个字节（32bit机器）用来存下一个还回来内存块的地址
};
struct TreeNode
{
    int _val;
    TreeNode *_left;
    TreeNode *_right;
    TreeNode()
        : _val(0), _left(nullptr), _right(nullptr)
    {
    }
};
// 测试对比调用new
// 申请多次上边的二叉树结点，并使用vector进行保存起来，方便释放结点
// 通过记录两者整个流程下来的时间，进行效率的比较
void TestObjectPool()
{
    // 申请释放的轮次
    const size_t Rounds = 3;
    // 每轮申请释放多少次
    const size_t N = 100000;
    std::vector<TreeNode *> v1;
    v1.reserve(N);

    size_t begin1 = clock();
    for (size_t j = 0; j < Rounds; ++j)
    {
        for (int i = 0; i < N; ++i)
        {
            v1.push_back(new TreeNode);
        }
        for (int i = 0; i < N; ++i)
        {
            delete v1[i];
        }
        v1.clear();
    }

    size_t end1 = clock();
    ObjectPool<TreeNode> TNPool;
    std::vector<TreeNode *> v2;
    v2.reserve(N);

    size_t begin2 = clock();
    for (size_t j = 0; j < Rounds; ++j)
    {
        for (int i = 0; i < N; ++i)
        {
            v2.push_back(TNPool.New());
        }
        for (int i = 0; i < N; ++i)
        {
            TNPool.Delete(v2[i]);
        }
        v2.clear();
    }
    size_t end2 = clock();
    cout << "new cost time:" << end1 - begin1 << endl;
    cout << "object pool cost time:" << end2 - begin2 << endl;
}